﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Windows.Forms;
using System.ComponentModel;
using System.Drawing;
using System.Net;

namespace ESpace
{
    [ToolboxBitmap(typeof(ETcpClient), "TcpClient.bmp")]
    [DefaultEvent("OnRecive")]
    public class ETcpClient : Component
    {
        //TCP-клиент
        TcpClient client = null;
        NetworkStream NetStream = null;
        // буфер для данных
        byte[] recvBuffer;
        /// <summary>Все пришедшие данные (может быть часть телеграммы или несколько телеграмм подряд)</summary>
        List<byte> recvDataList = new List<byte>();
        Control ctrl;

        #region *** свойства **************************************************

        [EditorBrowsable(EditorBrowsableState.Never), Browsable(false)]
        public bool IsConnect { get { return client != null; } }

        String pServerIP = "127.0.0.1";
        [DefaultValue("127.0.0.1")]
        public String ServerIP
        {
            get { return pServerIP; }
            set { if (client == null)pServerIP = value; }
        }
        int pServerPort = 654;
        [DefaultValue(654)]
        public int ServerPort
        {
            get { return pServerPort; }
            set { if (client == null) pServerPort = value; }
        }
        int pPackSize = 102400; // размер пакета
        [DefaultValue(102400)]
        public int MaxPackSize
        {
            get
            {
                return pPackSize;
            }
            set
            {
                if (client == null)
                {
                    pPackSize = value;
                    recvBuffer = new byte[pPackSize];
                }
            }
        }

        public Control ControlMainThread
        {
            get { return ctrl; }
            set { ctrl = value; Log.MainCtrl = value; }
        }

        [EditorBrowsable(EditorBrowsableState.Never), Browsable(false)]
        public EndPoint EndPoint { get { if (client == null)return null; if (client.Client == null)return null; return client.Client.LocalEndPoint; } }

        #endregion *** свойства **************************************************

        #region *** LOG **************************************************************
        TcpLog Log;
        public String[] GetLog()
        {
            return Log.GetLog();
        }
        #endregion *** LOG **************************************************************

        public ETcpClient()
        {
            Log = new TcpLog(ctrl);
        }

        #region *** события **************************************************

        #region *** событие приема данных *************************
        // класс аргументов для события приема данных
        public class RecvEventArgs : EventArgs
        {
            public byte[] buf;
            public RecvEventArgs(byte[] buf)
            {
                this.buf = buf;
            }
        }
        // делегат для события передачи данных
        public delegate void OnReciveDelegate(Object Sender, RecvEventArgs e);
        // переменная делегата (то бишь название функции), которая будет вызываться 
        public event OnReciveDelegate Recive;

        // генерация события приема данных
        delegate void OnReciveEventDelegate(byte[] buf);
        void OnRecive(byte[] buf)
        {
            // если для делегата есть функция, вызываем ее
            if (Recive != null)
                Recive(this, new RecvEventArgs(buf));
        }
        #endregion *** событие приема данных *************************

        #region *** событие дисконнекта *************************
        // делегат для события
        public delegate void OnDisconnectDelegate(Object Sender);
        // переменная делегата (то бишь название функции), которая будет вызываться 
        public event OnDisconnectDelegate DisconnectFromServer;

        // генерация события
        delegate void OnDisconnectEventDelegate();
        void OnDisconnect()
        {
            // если есть функция, вызываем ее
            if (DisconnectFromServer != null)
                DisconnectFromServer(this);
        }
        #endregion *** событие дисконнекта *************************

        #endregion *** события **************************************************

        /// <summary> Подключение к серверу. Возвращает удалось ли подключиться к серверу.
        /// Вернет false, если клиент уже подключен или не удалось подключиться</summary>
        public bool Connect()
        {
            if (client != null) return false;
            try
            {
                recvBuffer = new byte[pPackSize];
                client = new TcpClient();
                client.Connect(pServerIP, pServerPort);
                NetStream = client.GetStream();
                NetStream.BeginRead(recvBuffer, 0, recvBuffer.Length, new AsyncCallback(EndReceive), NetStream);
                Log.WriteLog("Подключение к серверу: " + client.Client.RemoteEndPoint.ToString() + " Клиент: " + client.Client.LocalEndPoint.ToString());
                return true;
            }
            catch (Exception ex)
            {
                Log.WriteLog("Подключение к серверу: Не удалось подключиться к серверу " + pServerIP + ":" + pServerPort.ToString() + ". " + ex);
                client = null;
                return false;
            }
        }

        public delegate void DisconnectDelegate();
        /// <summary>отключение от сервера</summary>
        public void Disconnect()
        {
            if (client == null) return;
            try
            {
                if (client != null)
                {
                    if (client.Connected)
                    {
                        Log.WriteLog("Отключение от сервера: " + client.Client.RemoteEndPoint.ToString() + " Клиент: " + client.Client.LocalEndPoint.ToString());
                        client.Close();
                    }
                    client = null;
                }
            }
            catch (Exception ex)
            {
                Log.WriteLog("Ошибка отключения от сервера: " + ex.Message);
                client = null;
            }
            OnDisconnect();
        }

        //завершение процесса чтения из Socket
        void EndReceive(IAsyncResult ia)
        {
            try
            {
                NetworkStream stream = (NetworkStream)ia.AsyncState;
                if (stream == null) return;
                int len = 0;
                try
                {
                    len = stream.EndRead(ia);
                }
                catch
                {
                    Log.WriteLog("Прием данных: Сервер принудительно отключил клиента или произошла ошибка. Клиент отключен");
                    if (ctrl.InvokeRequired) ctrl.Invoke(new DisconnectDelegate(Disconnect));
                    else Disconnect();
                    return;
                }
                if (len == 0)
                {
                    Log.WriteLog("Прием данных: !!!приняты данные длиной 0");
                }
                else
                {
                    byte[] recv = new byte[len];
                    Array.Copy(recvBuffer, 0, recv, 0, recv.Length);
                    Log.WriteLog("Прием данных: приняты данные длиной " + len.ToString());
                    recvDataList.AddRange(recv);
                    GetTelegramm();
                    // снова ожидаем передачи данных
                    stream.BeginRead(recvBuffer, 0, recvBuffer.Length, new AsyncCallback(EndReceive), stream);
                }
            }
            catch (Exception ex)
            {
                Log.WriteLog("Ошибка приема данных: " + ex.Message);
            }
        }

        /// <summary>Получение одной или нескольких телеграмм из общего буфера</summary>
        void GetTelegramm()
        {
            while (recvDataList.Count > 0)
            {
                if (recvDataList.Count == 0) return;
                if (recvDataList.Count < 4)
                {
                    Log.WriteLog("Ошибка разбора телеграммы: В буфере менее 4-х байт");
                    recvDataList.Clear();
                    return;
                }
                using (System.IO.MemoryStream ms = new System.IO.MemoryStream(recvDataList.ToArray()))
                using (System.IO.BinaryReader br = new System.IO.BinaryReader(ms))
                {
                    int len = br.ReadInt32();
                    if (len > pPackSize)
                    {
                        Log.WriteLog("Ошибка разбора телеграммы: слишком большая длина телеграммы в буфере " + len);
                        recvDataList.Clear();
                        return;
                    }

                    if (recvDataList.Count >= (len + 4))
                    {
                        byte[] telData = new byte[len];
                        br.Read(telData, 0, len);
                        recvDataList.RemoveRange(0, len + 4);
                        Log.WriteLog("Прием телеграммы: принята телеграмма длиной " + len.ToString());
                        // вызов события принятия телеграммы
                        if (ctrl.InvokeRequired) ctrl.Invoke(new OnReciveEventDelegate(OnRecive), telData);
                        else OnRecive(telData);
                    }
                    else
                        return;
                }
            }
        }

        /// <summary>Посылка данных на сервер.
        /// Если отсылка данных не удалась, клиент автоматически отключается от сервера и вызывается событие дисконнекта</summary>
        public bool Send(byte[] buf)
        {
            try
            {
                byte[] sendData;
                using (System.IO.MemoryStream ms = new System.IO.MemoryStream())
                using (System.IO.BinaryWriter bw = new System.IO.BinaryWriter(ms))
                {
                    bw.Write(buf.Length);
                    bw.Write(buf);
                    ms.SetLength(buf.Length + 4);
                    sendData = ms.ToArray();
                }
                client.Client.Send(sendData);
                Log.WriteLog(string.Format("Отправлен пакет на сервер, длиной {0} (общей длиной {1})", buf.Length, sendData.Length));
                return true;
            }
            catch (Exception ex)
            {
                Log.WriteLog("Невозможно передать пакет:" + ex.Message);
                Disconnect();
                return false;
            }
        }

    }
}
